package com.atguigu.gmall.product.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.common.cache.GmallCache;
import com.atguigu.gmall.common.constant.MqConst;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.service.RabbitService;
import com.atguigu.gmall.model.product.*;
import com.atguigu.gmall.product.mapper.*;
import com.atguigu.gmall.product.service.ManageService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author mqx
 * @date 2020-7-24 11:10:50
 */
@Service
public class ManageServiceImpl implements ManageService {

    // 引入mapper
    @Autowired
    private BaseCategory1Mapper baseCategory1Mapper;

    @Autowired
    private BaseCategory2Mapper baseCategory2Mapper;

    @Autowired
    private BaseCategory3Mapper baseCategory3Mapper;

    @Autowired
    private BaseAttrInfoMapper baseAttrInfoMapper;

    @Autowired
    private BaseAttrValueMapper baseAttrValueMapper;

    @Autowired
    private SpuInfoMapper spuInfoMapper;

    @Autowired
    private BaseSaleAttrMapper baseSaleAttrMapper;

    @Autowired
    private SpuImageMapper spuImageMapper;

    @Autowired
    private SpuSaleAttrValueMapper spuSaleAttrValueMapper;

    @Autowired
    private SpuSaleAttrMapper spuSaleAttrMapper;

    @Autowired
    private SkuInfoMapper skuInfoMapper;

    @Autowired
    private SkuImageMapper skuImageMapper;

    @Autowired
    private SkuSaleAttrValueMapper skuSaleAttrValueMapper;

    @Autowired
    private SkuAttrValueMapper skuAttrValueMapper;

    @Autowired
    private BaseCategoryViewMapper baseCategoryViewMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private BaseTrademarkMapper baseTrademarkMapper;

    @Autowired
    private RabbitService rabbitService;


    @Override
    public List<BaseCategory1> getCategory1() {
        // select * from base_category1
        return baseCategory1Mapper.selectList(null);
    }

    @Override
    public List<BaseCategory2> getCategory2(Long category1Id) {
        // select * from base_category2 where category1_id = category1Id
        return baseCategory2Mapper.selectList(new QueryWrapper<BaseCategory2>().eq("category1_id",category1Id));
    }

    @Override
    public List<BaseCategory3> getCategory3(Long category2Id) {
        // select * from base_category3 where category2_id = category2Id
        return baseCategory3Mapper.selectList(new QueryWrapper<BaseCategory3>().eq("category2_id",category2Id));
    }

    @Override
    public List<BaseAttrInfo> getAttrInfoList(Long category1Id, Long category2Id, Long category3Id) {
        // 查询平台属性列表.需要编写一个复杂的sql. 多表管理查询{目的是为了后续有个功能,能和现在这个功能结合到一起}
        // 这个功能,就是通过分类Id 查询平台属性,同时也可以查询平台属性值.
        // 如果不跟后面的功能进行整合,那么只需要单表查询就可以了.
        // 借助mybatis的配置文件方式进行多表关联查询.
        return baseAttrInfoMapper.selectBaseAttrInfoList(category1Id,category2Id,category3Id);
    }

    @Override
    @Transactional()
    public void saveAttrInfo(BaseAttrInfo baseAttrInfo) {
        // 这个实现类中,既有保存,又有修改!
        // baseAttrInfo 平台属性
        // 判断:
        if (baseAttrInfo.getId()!=null){
            baseAttrInfoMapper.updateById(baseAttrInfo);
        }else {
            baseAttrInfoMapper.insert(baseAttrInfo);
        }

        // baseAttrValue 平台属性值
        // 修改平台属性值的时候,系统无法确认用户想修改的数据是谁! 像这样的方式.我们可以通过另外一种方式修改:
        // 先删除,在新增!
        // 根据平台属性Id 进行对应的属性值删除,然后再做新增.
        // delete from base_attr_value where attr_id = baseAttrInfo.id
        baseAttrValueMapper.delete(new QueryWrapper<BaseAttrValue>().eq("attr_id",baseAttrInfo.getId()));

        // 获取到平台属性中的属性值集合
        List<BaseAttrValue> attrValueList = baseAttrInfo.getAttrValueList();
        // 判断当前集合不能为空!
        // 获取到平台属性中的属性值集合,循环遍历将数据添加到平台属性值表中!
        if (!CollectionUtils.isEmpty(attrValueList)){
            // 循环遍历
            for (BaseAttrValue baseAttrValue : attrValueList) {
                // 细节处理: 需要给attrId 赋值. attrId = baseAttrInfo.id
                baseAttrValue.setAttrId(baseAttrInfo.getId());
                baseAttrValueMapper.insert(baseAttrValue);
            }
        }
    }

    @Override
    public List<BaseAttrValue> getAttrValueList(Long attrId) {
        // select * from base_attr_value where attr_id = attrId
        QueryWrapper<BaseAttrValue> baseAttrValueQueryWrapper = new QueryWrapper<>();
        baseAttrValueQueryWrapper.eq("attr_id",attrId);
        return baseAttrValueMapper.selectList(baseAttrValueQueryWrapper);
    }

    @Override
    public BaseAttrInfo getBaseAttrInfo(Long attrId) {
        // select * from base_attr_info where id = attrId
        BaseAttrInfo baseAttrInfo = baseAttrInfoMapper.selectById(attrId);
        if (null!=baseAttrInfo){
            // 获取平台属性值集合,放入attrValueList 属性中!
            baseAttrInfo.setAttrValueList(getAttrValueList(attrId));
        }
        return baseAttrInfo;
    }

    @Override
    public IPage<SpuInfo> getSpuInfoPage(Page<SpuInfo> param, SpuInfo spuInfo) {
        QueryWrapper<SpuInfo> spuInfoQueryWrapper = new QueryWrapper<>();
        spuInfoQueryWrapper.eq("category3_id",spuInfo.getCategory3Id());
        // 设置一个排序规则
        spuInfoQueryWrapper.orderByDesc("id");
        // 调用mapper
        return spuInfoMapper.selectPage(param,spuInfoQueryWrapper);
    }

    @Override
    public List<BaseSaleAttr> getBaseSaleAttrList() {
        // 获取所有的销售属性数据
        List<BaseSaleAttr> baseSaleAttrList = baseSaleAttrMapper.selectList(null);
        return baseSaleAttrList;
    }

    @Override
    @Transactional
    public void saveSpuInfo(SpuInfo spuInfo) {
        /*
         保存数据。。
            spuInfo
            spuImage
            spuSaleAttr
            spuSaleAttrValue
         */
        // spuInfo
        spuInfoMapper.insert(spuInfo);
        // spuImage 获取前台传递过来的图片列表
        List<SpuImage> spuImageList = spuInfo.getSpuImageList();
        if (!CollectionUtils.isEmpty(spuImageList)){
            // 循环遍历
            for (SpuImage spuImage : spuImageList) {
                // 注意一个细节 spuId 页面没有传递，
                // 问题：第一次添加的时候，前台页面传递过来的spuInfo.id 为null .
                // 虽然你传递过来的时候给的是一个null ，但是 如果你插入了spuInfo 数据以后，那么这个id 就不为空了。
                // 因为spuInfo 实体类中id属性type = IdType.AUTO ，说明数据一旦插入以后能够获取到插入的Id。
                spuImage.setSpuId(spuInfo.getId());
                spuImageMapper.insert(spuImage);
            }
        }
        // spuSaleAttr
        List<SpuSaleAttr> spuSaleAttrList = spuInfo.getSpuSaleAttrList();
        if (!CollectionUtils.isEmpty(spuSaleAttrList)){
            // 循环遍历
            for (SpuSaleAttr spuSaleAttr : spuSaleAttrList) {
                spuSaleAttr.setSpuId(spuInfo.getId());
                spuSaleAttrMapper.insert(spuSaleAttr);

                // 获取属性值集合
                List<SpuSaleAttrValue> spuSaleAttrValueList = spuSaleAttr.getSpuSaleAttrValueList();
                if (!CollectionUtils.isEmpty(spuSaleAttrValueList)) {
                    // 循环遍历
                    for (SpuSaleAttrValue spuSaleAttrValue : spuSaleAttrValueList) {
                        spuSaleAttrValue.setSpuId(spuInfo.getId());
                        // 由于页面没有传递数据，但是，我们可以通过业务得知
                        // spu_sale_attr_value.sale_attr_name = spu_sale_attr.sale_attr_name
                        spuSaleAttrValue.setSaleAttrName(spuSaleAttr.getSaleAttrName());
                        spuSaleAttrValueMapper.insert(spuSaleAttrValue);
                    }
                }
            }
        }


    }

    @Override
    public List<SpuImage> getSpuImageList(Long spuId) {
        // select * from spuImage where spu_id = ?;
        return spuImageMapper.selectList(new QueryWrapper<SpuImage>().eq("spu_id",spuId));
    }

    @Override
    public List<SpuSaleAttr> getSpuSaleAttrList(Long spuId) {
        // 独立写 因为需要多表关联查询。此时不能单独使用mybatis-plus。 使用xml 配置文件了。
        List<SpuSaleAttr> spuSaleAttrList = spuSaleAttrMapper.selectSpuSaleAttrList(spuId);
        return spuSaleAttrList;
    }

    @Override
    @Transactional
    public void saveSkuInfo(SkuInfo skuInfo) {
        /*
        skuInfo
        skuSaleAttrValue
        skuAttrValue
        skuImage
         */
        //  skuInfo
        skuInfoMapper.insert(skuInfo);
        // skuImage 图片
        List<SkuImage> skuImageList = skuInfo.getSkuImageList();
        if (!CollectionUtils.isEmpty(skuImageList)){
            // 循环遍历
            for (SkuImage skuImage : skuImageList) {
                // 细节：需要将skuId 赋值，因为页面没有传递
                skuImage.setSkuId(skuInfo.getId());
                skuImageMapper.insert(skuImage);
            }
        }

        // skuAttrValue sku 与平台属性值的关系
        List<SkuAttrValue> skuAttrValueList = skuInfo.getSkuAttrValueList();
        if (!CollectionUtils.isEmpty(skuAttrValueList)){
            // 循环遍历
            for (SkuAttrValue skuAttrValue : skuAttrValueList) {
                // 细节：需要将skuId 赋值，因为页面没有传递
                skuAttrValue.setSkuId(skuInfo.getId());
                skuAttrValueMapper.insert(skuAttrValue);
            }
        }
        // skuSaleAttrValue sku 与 销售属性值的关系
        List<SkuSaleAttrValue> skuSaleAttrValueList = skuInfo.getSkuSaleAttrValueList();
        if (!CollectionUtils.isEmpty(skuSaleAttrValueList)){
            // 循环遍历
            for (SkuSaleAttrValue skuSaleAttrValue : skuSaleAttrValueList) {
                // 页面传递过来的值，我们只用saleAttrValueId，但是我们还需要spuId,skuId.
                skuSaleAttrValue.setSkuId(skuInfo.getId());
                skuSaleAttrValue.setSpuId(skuInfo.getSpuId()); // 从哪里获取?
                skuSaleAttrValueMapper.insert(skuSaleAttrValue);
            }
        }

        // 发送数据
        // 发送什么消息才能实现商品上架： 在service-list 中upperGoods(Long skuId)
        rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_GOODS,MqConst.ROUTING_GOODS_UPPER,skuInfo.getId());

    }

    @Override
    public IPage<SkuInfo> getSkuList(Page<SkuInfo> skuInfoPage) {
        // 可以按照某个排序规则进行分页查询 order by id desc
        QueryWrapper<SkuInfo> skuInfoQueryWrapper = new QueryWrapper<>();
        skuInfoQueryWrapper.orderByDesc("id");
        // 调用mapper 的分页查询方法即可！
        return skuInfoMapper.selectPage(skuInfoPage,skuInfoQueryWrapper);
    }

    @Override
    public void onSale(Long skuId) {
        // is_sale = 1
        // update sku_info set is_sale = 1 where id=skuId
        SkuInfo skuInfo = new SkuInfo();
        skuInfo.setId(skuId);
        skuInfo.setIsSale(1);
        skuInfoMapper.updateById(skuInfo); // 动态更新

        // 发送什么消息才能实现商品上架： 在service-list 中upperGoods(Long skuId)
        rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_GOODS,MqConst.ROUTING_GOODS_UPPER,skuId);

    }

    @Override
    public void cancelSale(Long skuId) {
        // is_sale = 0
        // update sku_info set is_sale = 0 where id=skuId
        SkuInfo skuInfo = new SkuInfo();
        skuInfo.setId(skuId);
        skuInfo.setIsSale(0);
        skuInfoMapper.updateById(skuInfo);

        // 发送什么消息才能实现商品上架： 在service-list 中upperGoods(Long skuId)
        rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_GOODS,MqConst.ROUTING_GOODS_LOWER,skuId);

    }

    @Override
    @GmallCache(prefix = "sku")
    public SkuInfo getSkuInfo(Long skuId) {
        // 使用redisson 做分布式锁
        // return getSkuInfoRedisson(skuId);
        // 使用的是redis+lua 做分布式锁
        // return getSkuInfoRedisSet(skuId);

        return getSkuInfoDB(skuId);
    }

    private SkuInfo getSkuInfoRedisson(Long skuId) {
        // 声明一个skuInfo 对象
        SkuInfo skuInfo = null;
        try {
            //        if(true){
            //            // 缓存
            //        }else {
            //            // db --- 放入缓存！
            //        }
            // 定义一个缓存的key
            // key = sku:skuId:info
            String skuKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;
            // 获取缓存的数据
            skuInfo = (SkuInfo) redisTemplate.opsForValue().get(skuKey);
            if (skuInfo==null){
                // 说明缓存中没有数据，db --- 放入缓存！
                // 使用redisson 做分布式锁
                // 定义锁的lockKey = sku:skuId:lock
                String lockKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKULOCK_SUFFIX;
                // 获取锁对象
                RLock lock = redissonClient.getLock(lockKey);
                /*
                第一种：lock.lock();
                第二种：lock.lock(10, TimeUnit.SECONDS);
                第三种：boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
                 */
                boolean flag = lock.tryLock(RedisConst.SKULOCK_EXPIRE_PX1, RedisConst.SKULOCK_EXPIRE_PX2, TimeUnit.SECONDS);
                // 判断
                if (flag){
                    // ctrl+alt+t 提示使用哪种方式来封装哪些代码块
                    try {
                        // 返回true 则上锁成功！
                        skuInfo = getSkuInfoDB(skuId); // 可能会出现什么问题？
                        // 缓存穿透！
                        if (skuInfo==null){
                            // 设置一个空对象进去,这个数据应该给一个短暂的过期时间
                            SkuInfo skuInfo1 = new SkuInfo();
                            redisTemplate.opsForValue().set(skuKey,skuInfo1,RedisConst.SKUKEY_TEMPORARY_TIMEOUT,TimeUnit.SECONDS);
                            // 返回不存在的对象
                            return skuInfo1;
                        }
                        // 将从数据库查询到数据放入缓存 商品详情页面数据，应该有个过期时间
                        redisTemplate.opsForValue().set(skuKey,skuInfo,RedisConst.SKUKEY_TIMEOUT,TimeUnit.SECONDS);
                        // 返回数据
                        return skuInfo;
                    } finally {
                        lock.unlock();
                    }
                }else{
                    // 自旋等待
                    Thread.sleep(1000);
                    return getSkuInfo(skuId);
                }
            }else {
                // 直接从缓存获取数据
                /*
                    skuInfo 表示谁? 缓存中的数据
                    数据库mysql中有 的skuId . 35,36,37
                    数据库mysql中没有 的数据skuId。138 如果缓存到了138 ，那么这个对象中所有的属性值是什么? null
                        页面渲染就是空白 前端提示查询的商品不存在！或者直接跳转到首页。

                     if(skuInfo.getId==null){ 138
                        说明当前商品在数据库中不存在！
                     }
                     skuInfo; 商品在数据库中存在！ 35，36，37
                     从结果上说：如果没有特殊的要求只是对页面进行渲染的话。那么不用判断也可以！

                     缓存数据的时候：查询到的skuInfo 如果是138，在数据库不存在的！
                        将这个数据放入缓存，但是这个时候，千万不能给这种缓存添加太长的过期时间。一定要将过期时间设置的很短。
                     如果此时，后台有管理员添加了138 这个商品。
                        首先，在过期时间范围外，那么是没有任何问题的！
                        如果在这个过期时间范围内，那么查询的数据应该是缓存中的空数据。

                        对于操作缓存和数据库同步的时候！需要有一个原则：{尽量保证数据库与缓存一致}
                        方案1：同步操作
                            在对数据库操作DML语言的时候，insert,update,delete 那么我们可以先完成db，
                                然后将缓存给删除！
                        方案2：异步操作
                            只操作数据库，可以使用消息队列来发送异步通知来删除缓存数据。

                        查询数据库中不存在的商品：那么我们的控制器必然会错误提示！
                 */
                return skuInfo;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return getSkuInfoDB(skuId);
    }

    // 表示使用redis set+lua 脚本实现的分布式锁！
    private SkuInfo getSkuInfoRedisSet(Long skuId) {
        SkuInfo skuInfo = null;
        try {
            // 添加缓存部分！ 先查询缓存，如果缓存没有再查询数据，并将数据放入缓存！
            //        if(true){
            //            // 缓存
            //        }else {
            //            // db --- 放入缓存！
            //        }
            // 定义一个缓存的key
            // key = sku:skuId:info
            String skuKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;
            // 获取缓存的数据
            skuInfo = (SkuInfo) redisTemplate.opsForValue().get(skuKey);
            // 判断缓存中的数据是否为空
            if (skuInfo==null){
                // 说明缓存没有数据，没有数据, db --- 放入缓存!
                // 在从数据库获取数据的时候，我们需要添加一个锁，为了保证在高并发情况下防止缓存击穿。
                //  redis - set ，lua 脚本  | 使用Redissson
                //  lockKey = sku:skuId:lock
                String lockKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKULOCK_SUFFIX;
                // 设置UUID
                String uuid = UUID.randomUUID().toString();
                // 执行上锁命令
                Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, uuid, RedisConst.SKULOCK_EXPIRE_PX1, TimeUnit.SECONDS);
                // 判断上锁是否成功
                if (flag){ // 表示上锁成功了！
                    skuInfo = getSkuInfoDB(skuId); // 可能会出现什么问题？
                    // 缓存穿透！
                    if (skuInfo==null){
                        // 设置一个空对象进去,这个数据应该给一个短暂的过期时间
                        SkuInfo skuInfo1 = new SkuInfo();
                        redisTemplate.opsForValue().set(skuKey,skuInfo1,RedisConst.SKUKEY_TEMPORARY_TIMEOUT,TimeUnit.SECONDS);
                        // 返回不存在的对象
                        return skuInfo1;
                    }
                    // 将从数据库查询到数据放入缓存 商品详情页面数据，应该有个过期时间
                    redisTemplate.opsForValue().set(skuKey,skuInfo,RedisConst.SKUKEY_TIMEOUT,TimeUnit.SECONDS);
                    // 获取完成之后：删除对应的锁！ 使用lua 脚本
                    // 这个脚本只在客户端传入的值和键的口令串相匹配时
                    String script="if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                    // 执行这个lua 脚本
                    DefaultRedisScript defaultRedisScript = new DefaultRedisScript();
                    // 将lua脚本放入对象
                    defaultRedisScript.setScriptText(script);
                    // 设置一个返回值
                    defaultRedisScript.setResultType(Long.class);
                    redisTemplate.execute(defaultRedisScript, Arrays.asList(lockKey),uuid);
                    // 返回真正的数据
                    return skuInfo;
                }else {
                    // 睡眠 表示这个线程没有获取到锁！
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 继续获取数据
                    return getSkuInfo(skuId);
                }
            }else {
                // 表示缓存中有数据！ 从缓存中获取
                return skuInfo;
            }
        } catch (Exception e) {
            // 如果有日志系统的话，那么这个地方应该记录日志。
            e.printStackTrace();
        }
        // 如果缓存宕机了？redisTemplate 以及获取数据的时候都会出现异常！
        return getSkuInfoDB(skuId);
    }

    // 根据skuId 获取skuInfo以及skuImage 数据。
    // ctrl+alt+m
    private SkuInfo getSkuInfoDB(Long skuId) {
        // skuId = skuInfo.id
        SkuInfo skuInfo = skuInfoMapper.selectById(skuId);

        if(skuInfo!=null){
            // 获取商品的图片信息
            // select * from sku_image where sku_id=skuId
            List<SkuImage> skuImageList = skuImageMapper.selectList(new QueryWrapper<SkuImage>().eq("sku_id", skuId));
            skuInfo.setSkuImageList(skuImageList);
        }
        // 返回skuInfo 对象数据！
        return skuInfo;
    }

    @Override
    @GmallCache(prefix = "categoryView")
    public BaseCategoryView getCategoryViewByCategory3Id(Long category3Id) {
        // 视图中的 id = category3_id
        return baseCategoryViewMapper.selectById(category3Id);
    }

    @Override
    @GmallCache(prefix = "price")
    public BigDecimal getSkuPrice(Long skuId) {
        SkuInfo skuInfo = skuInfoMapper.selectById(skuId);
        if (null!=skuInfo){
            return skuInfo.getPrice();
        }
        return new BigDecimal("0");
    }

    @Override
    @GmallCache(prefix = "spuSaleAttrListCheckBySku")
    public List<SpuSaleAttr> getSpuSaleAttrListCheckBySku(Long skuId, Long spuId) {
        // 调用mapper 查询数据
        return spuSaleAttrMapper.selectSpuSaleAttrListCheckBySku(skuId,spuId);
    }

    @Override
    @GmallCache(prefix = "skuValueIdsMap")
    public Map getSkuValueIdsMap(Long spuId) {
        // 创建一个Map 对象
        HashMap<Object, Object> map = new HashMap<>();
        // map.put("key","value")
        // key = "78|75" value = 37
        // 获取key ，value 所对应的数据 ，通过我们刚才写的sql 语句。
        List<Map> mapList = skuSaleAttrValueMapper.selectSaleAttrValuesBySpu(spuId);
        if (!CollectionUtils.isEmpty(mapList)){
            // 循环遍历
            for (Map skuMap : mapList) {
                map.put(skuMap.get("value_ids"),skuMap.get("sku_id"));
            }
        }
        return map;
    }

    @Override
    @GmallCache(prefix = "getBaseCategoryList")
    public List<JSONObject> getBaseCategoryList() {
        // 声明一个Json 对象的集合
        List<JSONObject> list = new ArrayList<>();
        // 获取所有的分类数据 ：一级，二级，三级
        List<BaseCategoryView> baseCategoryViewList = baseCategoryViewMapper.selectList(null);
        // 获取一级，二级，三级分类中的部分数据！
        // 按照一级分类Id 进行分组 Collectors.groupingBy(BaseCategoryView::getCategory1Id)
        // key = 一级分类Id ，value = List<BaseCategoryView>
        Map<Long, List<BaseCategoryView>> category1Map = baseCategoryViewList.stream().collect(Collectors.groupingBy(BaseCategoryView::getCategory1Id));
        // 构建Json 数据了。
        int index = 1;
        // 一级分类Id 的名称categoryName等数据。
        for (Map.Entry<Long, List<BaseCategoryView>> entry1 : category1Map.entrySet()) {
            // 获取一级分类Id
            Long category1Id = entry1.getKey();

            // 获取一级分类的categoryName
            List<BaseCategoryView> category2List = entry1.getValue();
            String category1Name = category2List.get(0).getCategory1Name();
            // 声明一个JSONObject 将一级分类数据存储上
            JSONObject category1 = new JSONObject();

            category1.put("index",index);
            category1.put("categoryName",category1Name);
            category1.put("categoryId",category1Id);

            // category1.put("categoryChild",category2List); 因为二级分类数据暂时没有，所以先停止赋值。
            // 让index 进行++操作，更新一级分类的个数。
            index++;

            // 声明一个集合来存储一级分类下所有的二级分类数据
            List<JSONObject> category2Child = new ArrayList<>();
            // 获取二级分类数据！ key = 二级分类Id value = List<BaseCategoryView>
            Map<Long, List<BaseCategoryView>> category2Map = category2List.stream().collect(Collectors.groupingBy(BaseCategoryView::getCategory2Id));
            // 循环遍历category2Map 获取二级分类Id，二级分类名称
            for (Map.Entry<Long, List<BaseCategoryView>> entry2 : category2Map.entrySet()) {
                // 二级分类Id
                Long category2Id = entry2.getKey();
                // 二级分类的名称，获取value中的数据。
                List<BaseCategoryView> category3List = entry2.getValue();
                // 因为按照二级分类id进行分组了，所以后面的集合中的getCategory2Name 是一样的。
                String category2Name = category3List.get(0).getCategory2Name();
                // 声明一个JSONObject 将二级分类数据存储上
                JSONObject category2 = new JSONObject();
                category2.put("categoryName",category2Name);
                category2.put("categoryId",category2Id);
                // 二级分类下还有三级分类的集合,三级分类数据还没有获取到！
                // category2.put("categoryChild",category3List);

                // 将每次循环的二级分类数据，放入了这个集合
                category2Child.add(category2);

                // 声明一个集合来存储一级分类下所有的二级分类数据
                List<JSONObject> category3Child = new ArrayList<>();
                // 获取三级分类数据 由于三级分类Id 没有重复的，不需要分组了。
                // Consumer  void accept(T t);
                category3List.stream().forEach(category3View ->{
                    // 声明一个JSONObject 将三级分类数据存储上
                    JSONObject category3 = new JSONObject();
                    // 获取三级分类Id
                    category3.put("categoryId",category3View.getCategory3Id());
                    // 获取到三级分类的名称
                    category3.put("categoryName",category3View.getCategory3Name());
                    // 将获取到的三级分类数Id 放入集合。
                    category3Child.add(category3);
                });
                // 将三级分类数据集合放入二级分类数据
                 category2.put("categoryChild",category3Child);
            }
            // 将二级分类数据集合放入一级分类数据
             category1.put("categoryChild",category2Child);

            // 将所有的一级分类数据放入到Json 集合中
            list.add(category1);
        }
        // 返回Json 对象集合
        return list;
    }

    @Override
    public BaseTrademark getBaseTrademarkById(Long tmId) {
        return baseTrademarkMapper.selectById(tmId);
    }

    @Override
    public List<BaseAttrInfo> getAttrList(Long skuId) {
        // 多表关联查询
        return baseAttrInfoMapper.selectBaseAttrInfoListBySkuId(skuId);
    }

}
